#include "mboard.h"
#include "mboard_private.h"
#include "i2c_hw.h"
#include "gpio_hw.h"
#include "dma_hw.h"
#include "rcc.h"
#include "nvic.h"
#include "mdebug.h"
#include <string.h>

/**
 * I2C1,MSB,有START、ADDR、R(1)/W(0)、ACK、STOP等信号
 *   PB8 -- SCL，板子上拉4.7K
 *   PB9 -- SDA，板子上拉4.7K
 *   1、I2C作主机时，设备的地址需要手动写入DR寄存器，同时写入DR的值会影响到I2C
 *      的行为，比如11110xx0序列表示发送的是10bits地址，会产生ADDR10标志，地址
 *      的最后一位会被解读为读(1)写(0)动作，当ADDR标志清除后，I2C主机即进入读
 *      或写模式。
 *   2、I2C默认均处于机模式，仅设置CR1.START才会切换到主机模式：
 *
 * AT24C02，手册中说该型号已不推荐用于新的设计，可采用AT24C01A/04替代
 *   1、A0/1/2=0，地址为0x50
 *   2、引脚，板子上WP接GND，表示不进行写保护
 *      +-----+-----+-----+
 *      | A0  | 1 8 | VCC |
 *      | A1  | 2 7 | WP  |
 *      | A2  | 3 6 | SCL |
 *      | GND | 4 5 | SDA |
 *      +-----+-----+-----+
 *   3、容量：8*256=2Kb=256B，PAGE：8字节，1百万次擦写，100年数据保持
 *   4、支持400KHz通信频率，支持2.7-5.5V供电，5mA
 *   5、支持字节写，或PAGE写，EEPROM会在停止位发起后开始真正的写，写入期间不再
 *      响应I2C的请求，超过一个PAGE的地址边界，会回环到PAGE开始的地方;
 *   6、EEPROM中有维护一个读写指针，它始终指向要操作的字节，当写入1个字节后，
 *      它将自增并指向下一个字节。EEPROM读数据即读出该指针指向的数据，可以读一
 *      个字节或多个连续的字节，EEPROM每输出一个字节，该指针加1，当指针直到末
 *      尾时会回环到0地址，主机不想读的时候需要回复一个NACK，然后发一个停止位。
 *      主机想要随机读的时候，需要发送一次写操作，但是不发送要写的数据，发送
 *      完字节地址后直接发送停止位，其目的是修改读写指针，然后读取数据即为该
 *      地址的数据，当然写操作的停止位和读操作的起始位可以合并为1个重新起始位。
 *   7、单次写最大5ms
 *   8、发送9个时钟，期间SDA保持高电平，再生成一个起始位，可以让EEPROM复位
 */

#define EEPROM_READ    ((0x50<<1)|0x1)
#define EEPROM_WRITE   ((0x50<<1)|0x0)
#define EEPROM_PAGE    (0x8U)

static void mI2cClockInit(void)
{
	/* 使能I2C1时钟 */
	RCC_EnableApb1(RCC_CLK_APB1_I2C1);
	RCC_EnableAhb1(RCC_CLK_AHB1_GPIOB);
}

static void mI2cGpioInit(void)
{
	uint32_t tmp32;

	/* 复用模式 */
	tmp32 = SOC_GPIOB.MODER;
	tmp32 &= ~GPIO_MODER_MASK(8);
	tmp32 &= ~GPIO_MODER_MASK(9);
	tmp32 |= GPIO_MODER_AF(8);
	tmp32 |= GPIO_MODER_AF(9);
	SOC_GPIOB.MODER = tmp32;

	/* 开漏输出 */
	tmp32 = SOC_GPIOB.OTYPER;
	tmp32 &= ~GPIO_OTYPER_MASK(8);
	tmp32 &= ~GPIO_OTYPER_MASK(9);
	tmp32 |= GPIO_OTYPER_OD(8);
	tmp32 |= GPIO_OTYPER_OD(9);
	SOC_GPIOB.OTYPER = tmp32;

	/* 2M的输出速度 */
	tmp32 = SOC_GPIOB.OSPEEDR;
	tmp32 &= ~GPIO_OSPEEDR_MASK(8);
	tmp32 &= ~GPIO_OSPEEDR_MASK(9);
	tmp32 |= GPIO_OSPEEDR_2M(8);
	tmp32 |= GPIO_OSPEEDR_2M(9);
	SOC_GPIOB.OSPEEDR = tmp32;

	/* 禁用上下拉 */
	tmp32 = SOC_GPIOB.PUPDR;
	tmp32 &= ~GPIO_PUPDR_MASK(8);
	tmp32 &= ~GPIO_PUPDR_MASK(9);
	tmp32 |= GPIO_PUPDR_NONE(8);
	tmp32 |= GPIO_PUPDR_NONE(9);
	SOC_GPIOB.PUPDR = tmp32;

	/* IO复用为I2C1 */
	tmp32 = SOC_GPIOB.AFHR;
	tmp32 &= ~GPIO_AFHR_MASK(8);
	tmp32 &= ~GPIO_AFHR_MASK(9);
	tmp32 |= GPIO_AFHR_0_15D(8, 4);
	tmp32 |= GPIO_AFHR_0_15D(9, 4);
	SOC_GPIOB.AFHR = tmp32;
}

const uint32_t CR1 = I2C_CR1_PE_DIS |     // 禁用外设，等配置完毕后再使能
	                 I2C_CR1_MODE_I2C |   // I2C模式，非SMBus
	                 I2C_CR1_ENPEC_DIS |  // 禁用PEC(数据包错误校验)
	                 I2C_CR1_ENGC_DIS |   // 禁用广播呼叫，对地址00h应答
	                 I2C_CR1_STRETCH_EN | // 使能时钟延长
	                 I2C_CR1_START_DIS |  // 禁止发送起始位
	                 I2C_CR1_STOP_DIS |   // 禁止发送停止位
	                 I2C_CR1_ACK_DIS |    // 禁止发送ACK
	                 I2C_CR1_SWRST_DIS;   // 禁止软复位

static void mI2cDeviceInit(void)
{
	/* 配置I2C */
	SOC_I2C1.CR1 = CR1;
	SOC_I2C1.CR2 = I2C_CR2_FREQ_V_2_42D(42) | // APB的时钟是42MHz
	               I2C_CR2_ITERREN_DIS | // 禁止错误中断
	               I2C_CR2_ITEVTEN_DIS | // 禁止事件中断
	               I2C_CR2_DMAEN_DIS |   // 禁用DMA
	               I2C_CR2_LAST_DIS;     // 下一个数据不是最后一个，
	                                     // 用于主机模式DMA收数据时产生NACK
	/* 设定SCL的时钟为400KHz，APB=42MHz，占空比为1+2=3，
	   42MHz/3/400KHz=35 */
	SOC_I2C1.CCR = I2C_CCR_V_1_4095D(35) | // 高低电平计数值
	               I2C_CCR_DUTY_33 |       // 快速模式，高电平占空比33%
	               I2C_CCR_FS_FAST;        // 快速模式
	/* APB=42MHz，APB周期Tapb=2.4ns */
	SOC_I2C1.TRISE = I2C_TRISE_V_0_62D(50);  // 最大上升时间120ns，默认值为2
}

static void mI2cInterrupt(void)
{
	NVIC_SetPriority(NVIC_IRQ_I2C1_EV, NVIC_PRIORITY_LOW,
	                 NVIC_PRIORITY_SUB_NONE);
	NVIC_SetPriority(NVIC_IRQ_I2C1_ER, NVIC_PRIORITY_LOW,
	                 NVIC_PRIORITY_SUB_NONE);
	NVIC_EnableIrq(NVIC_IRQ_I2C1_EV);
	NVIC_EnableIrq(NVIC_IRQ_I2C1_ER);
}

static void mI2cDmaInit(void)
{
}

void mI2cInit(void)
{
	mI2cClockInit();
	mI2cGpioInit();
	mI2cDeviceInit();
	mI2cInterrupt();
	mI2cDmaInit();
}

void mI2cEepromRead(uint32_t addr, uint8_t *buf, uint32_t len)
{
	/* 等待总线空闲，START位会推迟到总线空闲时发送，故不需要这里等待 */
	//while (SOC_I2C1.SR2 & I2C_SR2_BUSY_EN);

	/* 发送START，I2C进入主模式 */
	SOC_I2C1.CR1 = CR1 | I2C_CR1_START_EN | I2C_CR1_PE_EN;
	/* 等到START完成，读清SB标志，需要先读SR1才能在DR中写入设备地址 */
	while (!(SOC_I2C1.SR1 & I2C_SR1_SB_EN));

	/* 发送EEPROM设备地址，并设置写标志 */
	SOC_I2C1.DR = EEPROM_WRITE;
	/* 等待设备地址发送完成并得到响应 */
	while (!(SOC_I2C1.SR1 & I2C_SR1_ADDR_EN));
	/* 读SR1后读SR2用于清除ADDR标志，之后硬件进入发送模式 */
	SOC_I2C1.SR2;

	/* 对于AT24C04/8等，地址超过256，则先发送高位 */
	if (addr >= 256) {
		/* 发送数据地址 */
		SOC_I2C1.DR = addr>>8;
		addr -= 256;
		/* 等待发送完成且收到ACK */
		while (!(SOC_I2C1.SR1 & I2C_SR1_TxE_EN));
	}

	/* 发送数据地址 */
	SOC_I2C1.DR = addr;
	/* 等待发送完成且收到ACK */
	while (!(SOC_I2C1.SR1 & I2C_SR1_TxE_EN));

	/* 发送RESTART */
	SOC_I2C1.CR1 |= I2C_CR1_START_EN;
	/* 等到START完成，读清SB标志，需要先读SR1才能在DR中写入设备地址 */
	while (!(SOC_I2C1.SR1 & I2C_SR1_SB_EN));

	/* 发送EEPROM设备地址，并设置读标志 */
	SOC_I2C1.DR = EEPROM_READ;
	/* 等待设备地址发送完成并得到响应，此时不能清除ADDR标志 */
	while (!(SOC_I2C1.SR1 & I2C_SR1_ADDR_EN));

	/* 需要根据接收数据的长度来配置I2C */
	if (len == 1) {
		/* 接收1个字节时，需要设置当前字节结束后发送NACK */
		SOC_I2C1.CR1 |= I2C_CR1_ACK_DIS;
		/* 读SR1后读SR2用于清除ADDR标志，之后硬件进入接收模式 */
		SOC_I2C1.SR2;
		/* 当前字节结束后发送停止位 */
		SOC_I2C1.CR1 |= I2C_CR1_STOP_EN;
		/* 等待收到1字节数据 */
		while (!(SOC_I2C1.SR1 & I2C_SR1_RxNE_EN));
		buf[0] = SOC_I2C1.DR;
	} else if (len == 2) {
		/* 接收2个字节时，需要设置下一个字节结束后发送NACK和STOP */
		SOC_I2C1.CR1 |= I2C_CR1_ACK_DIS | I2C_CR1_POS_NEXT;
		/* 读SR1后读SR2用于清除ADDR标志，之后硬件进入接收模式 */
		SOC_I2C1.SR2;
		/* 此时数据1在DR中，数据2在移位寄存器中 */
		while (!(SOC_I2C1.SR1 & I2C_SR1_BTF_EN));
		/* 发送停止位 */
		SOC_I2C1.CR1 |= I2C_CR1_STOP_EN;
		/* 读取2字节数据，此处不需要再判断RxNE */
		buf[0] = SOC_I2C1.DR;
		buf[1] = SOC_I2C1.DR;
	} else if (len > 2){
		/* 收到数据后发送ACK */
		SOC_I2C1.CR1 |= I2C_CR1_ACK_EN;
		/* 读SR1后读SR2用于清除ADDR标志，之后硬件进入接收模式 */
		SOC_I2C1.SR2;
		/* 正常接收N-3个数据 */
		len -= 3;
		while (len--) {
			while (!(SOC_I2C1.SR1 & I2C_SR1_RxNE_EN));
			*buf++ = SOC_I2C1.DR;
		}
		/* 此时数据N-2在DR中，数据N-1在移位寄存器中 */
		while (!(SOC_I2C1.SR1 & I2C_SR1_BTF_EN));
		/* 设置当前字节（即第N字节，N-1已经收到了）节后发送NACK */
		SOC_I2C1.CR1 &= ~I2C_CR1_ACK_EN;
		*buf++ = SOC_I2C1.DR;
		/* 此时数据N-1在DR中，数据N在移位寄存器中 */
		while (!(SOC_I2C1.SR1 & I2C_SR1_BTF_EN));
		/* 发送停止位 */
		SOC_I2C1.CR1 |= I2C_CR1_STOP_EN;
		/* 读取2字节数据，此处不需要再判断RxNE */
		buf[0] = SOC_I2C1.DR;
		buf[1] = SOC_I2C1.DR;
	}
}

static void mI2cEepromWriteOnce(uint32_t addr, const uint8_t *buf, uint32_t len)
{
	/* 等待总线空闲，START位会推迟到总线空闲时发送，故不需要这里等待 */
	//while (SOC_I2C1.SR2 & I2C_SR2_BUSY_EN);

	/* 发送START，I2C进入主模式 */
	SOC_I2C1.CR1 = CR1 | I2C_CR1_START_EN | I2C_CR1_PE_EN;
	/* 等到START完成，读清SB标志，需要先读SR1才能在DR中写入设备地址 */
	while (!(SOC_I2C1.SR1 & I2C_SR1_SB_EN));

	/* 发送EEPROM设备地址，并设置写标志 */
	SOC_I2C1.DR = EEPROM_WRITE;
	/* 等待设备地址发送完成并得到响应 */
	while (!(SOC_I2C1.SR1 & I2C_SR1_ADDR_EN));
	/* 读SR1后读SR2用于清除ADDR标志，之后硬件进入发送模式 */
	SOC_I2C1.SR2;

	/* 对于AT24C04/8等，地址超过256，则先发送高位 */
	if (addr >= 256) {
		/* 发送数据地址 */
		SOC_I2C1.DR = addr>>8;
		addr -= 256;
		/* 等待发送完成且收到ACK */
		while (!(SOC_I2C1.SR1 & I2C_SR1_TxE_EN));
	}

	/* 发送数据地址 */
	SOC_I2C1.DR = addr;
	/* 等待发送完成且收到ACK */
	while (!(SOC_I2C1.SR1 & I2C_SR1_TxE_EN));

	while (len--) {
		/* 发送数据 */
		SOC_I2C1.DR = *buf++;
		/* 等待发送完成且收到ACK */
		while (!(SOC_I2C1.SR1 & I2C_SR1_TxE_EN));
	}

	/* 当数据发送完成时发送停止位 */
	SOC_I2C1.CR1 |= I2C_CR1_STOP_EN;

	/* 等待数据发送完成 */
	while (!(SOC_I2C1.SR1 & I2C_SR1_BTF_EN));

	/* 等待写完成，EEPROM写期间不会响应任何总线 */
WAIT:
	/* 发送START，I2C进入主模式 */
	SOC_I2C1.CR1 = CR1 | I2C_CR1_START_EN | I2C_CR1_PE_EN;
	/* 等到START完成，读清SB标志，需要先读SR1才能在DR中写入设备地址 */
	while (!(SOC_I2C1.SR1 & I2C_SR1_SB_EN));

	/* 发送EEPROM设备地址，并设置写标志 */
	SOC_I2C1.DR = EEPROM_WRITE;
	/* 等待设备地址发送完成并得到响应 */
	while (!(SOC_I2C1.SR1 & I2C_SR1_ADDR_EN)) {
		/* 无应答标志，需要写0清 */
		if (SOC_I2C1.SR1 & I2C_SR1_AF_EN) {
			SOC_I2C1.SR1 = 0;
			goto WAIT;
		}
	}

	/* 发送停止位 */
	SOC_I2C1.CR1 |= I2C_CR1_STOP_EN;
	/* 读SR2清除ADDR标志 */
	SOC_I2C1.SR2;
}

void mI2cEepromWrite(uint32_t addr, const uint8_t *buf, uint32_t len)
{
	uint32_t tmp1;
	uint32_t tmp2;

	/* 起始地址是否PAGE对齐 */
	tmp1 = addr & (EEPROM_PAGE-1);
	if (tmp1) {
		/* 计算当前PAGE剩余空间 */
		tmp2 = EEPROM_PAGE - tmp1;
		if (tmp2 >= len) {
			/* 剩余空间足够，则一次性写完 */
			mI2cEepromWriteOnce(addr, buf, len);
			return;
		} else {
			/* 写满当前PAGE */
			mI2cEepromWriteOnce(addr, buf, tmp2);
			buf += tmp2;
			len -= tmp2;
			addr += tmp2;
		}
	}
	/* 整PAGE写入 */
	while (len > EEPROM_PAGE) {
		mI2cEepromWriteOnce(addr, buf, EEPROM_PAGE);
		buf += EEPROM_PAGE;
		len -= EEPROM_PAGE;
		addr += EEPROM_PAGE;
	}
	/* 如未写完，则写入剩余部分 */
	if (len) {
		mI2cEepromWriteOnce(addr, buf, len);
	}
}

/**
 * 系统掉电前应检查I2C总线是否空闲
 */
void mI2cWaitFinish(void)
{
	/* 等待总线空闲 */
	while (SOC_I2C1.SR2 & I2C_SR2_BUSY_EN);
}

void mI2cTest(void)
{
	uint8_t dat[256];

	memset(dat, 0x1, sizeof(dat));
	mI2cEepromRead(0, dat, 32);
	mDebugPutMemoryForU8("000: ", dat, 32);
	memset(dat, 0x2, sizeof(dat));
	mI2cEepromWrite(0, dat, 32);
	memset(dat, 0x3, sizeof(dat));
	mI2cEepromRead(0, dat, 32);
	mDebugPutMemoryForU8("000: ", dat, 32);
	memset(dat, 0x4, sizeof(dat));
	mI2cEepromWrite(6, dat, 1);
	memset(dat, 0x5, sizeof(dat));
	mI2cEepromRead(0, dat, 32);
	mDebugPutMemoryForU8("000: ", dat, 32);
	memset(dat, 0x6, sizeof(dat));
	mI2cEepromWrite(12, dat, 10);
	memset(dat, 0x7, sizeof(dat));
	mI2cEepromRead(0, dat, 32);
	mDebugPutMemoryForU8("000: ", dat, 32);
	memset(dat, 0x8, sizeof(dat));
	mI2cEepromRead(5, &dat[5], 1);
	mDebugPutMemoryForU8("000: ", dat, 32);
	memset(dat, 0x9, sizeof(dat));
	mI2cEepromRead(7, &dat[7], 2);
	mDebugPutMemoryForU8("000: ", dat, 32);
	memset(dat, 0x10, sizeof(dat));
	mI2cEepromRead(9, &dat[9], 3);
	mDebugPutMemoryForU8("000: ", dat, 32);
}
